package cycles.util.jdbc;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.PreparedStatementCreator;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;

import cycles.util.jdbc.orm.DBField;
import cycles.util.jdbc.orm.DBTable;
import cycles.util.jdbc.orm.FieldMapType;

public class TableMapper<T> {
	private static final int INSERT_SQL_ID = 0;

	protected Class<T> clsdefine;// 对象类
	protected String tablename;// 表名
	protected String tblkey;// 表中的主键字段名
	protected Field keyfld;// 主键对应的对象字段
	protected boolean autokey;// 是否自增主键
	protected Map<String, Field> tbl2obj;// 数据库字段到对象字段的映射
	protected Map<Field, String> obj2tbl;// 对象字段到数据库字段的映射，目前没用到
	protected List<Field> insertFields;
	private RowMapper<T> rowMapper;
	protected JdbcTemplate jt;
	List<String> sqlCache = new ArrayList<String>();

	// Map<Integer, String> sqlCache = new HashMap<Integer, String>();

	/**
	 * 设置表名
	 * 
	 * @param tablename
	 */
	public void setTablename(String tablename) {
		this.tablename = tablename;
	}

	/**
	 * 
	 * @param jdbcTemplate
	 *            数据源
	 * @param clsdef
	 *            类定义
	 * @param tablename
	 */
	public TableMapper(JdbcTemplate jdbcTemplate, Class<T> clsdef,
			String tablename) {
		this.jt = jdbcTemplate;

		this.clsdefine = clsdef;
		DBTable anno = clsdef.getAnnotation(DBTable.class);

		if (tablename == null || tablename.trim().equals("")) {
			tablename = anno.value(); // 如果tablename未在外部设定，则使用注解声明的值
		}
		this.tblkey = anno.key();
		this.autokey = anno.autokey();
		Field[] flds = anno.inheritLevel() == 0 ? clsdef.getDeclaredFields() : //
				(anno.inheritLevel() == 1 ? clsdef.getSuperclass()
						.getDeclaredFields() : clsdef.getSuperclass()
						.getSuperclass().getDeclaredFields());

		insertFields = new ArrayList<Field>();

		tbl2obj = new HashMap<String, Field>();// 数据库字段到对象字段的映射
		obj2tbl = new HashMap<Field, String>();// 对象字段到数据库字段的映射
		for (Field fld : flds) {
			if (Modifier.isStatic(fld.getModifiers())) { // 静态变量不映射
				continue;
			}
			String fldname;
			DBField finfo = fld.getAnnotation(DBField.class);

			if (finfo == null || finfo.value() == FieldMapType.DEFAULT_MAP) {// 无标注或与字段名一致
				fldname = fld.getName().toLowerCase();
			} else if (finfo.value() == FieldMapType.NOT_IN_MAP) {// 不在数据库
				continue;
			} else {// 定义了dbfield
				fldname = finfo.dbfield();
			}

			if (anno.autokey() && fldname.equals(this.tblkey)) {
				this.keyfld = fld;// 如果是自增主键则在处理中跳过
			} else {
				this.insertFields.add(fld);
			}
			fld.setAccessible(true);
			tbl2obj.put(fldname, fld);
			obj2tbl.put(fld, fldname);
		}
		String insertSql = SQLCommand.INSERT.buildSql(tablename, obj2tbl,
				insertFields.toArray(new Field[insertFields.size()]), null,
				null, false, null);
		// insert 语句要cache起来
		sqlCache.add(insertSql);

		rowMapper = new RowMapper<T>() {
			@Override
			public T mapRow(ResultSet rs, int n) throws SQLException {
				ResultSetMetaData metaData = rs.getMetaData();
				T t = createInst();
				try {
					for (int i = 0; i < metaData.getColumnCount(); i++) {
						String fldname = metaData.getColumnName(i + 1);
						Field fld = tbl2obj.get(fldname);
						if (fld != null) {
							// TODO 这里可以进行扩展，以增加其他不在数据库映射的基本类型外
							// 如数组等的加载，可以参考m2j.txt进行修改
							fld.set(t, rs.getObject(i + 1));
						}
					}
					if (t instanceof IOrmInitor) {
						((IOrmInitor) t).init(rs);
					}
				} catch (Exception e) {
					e.printStackTrace();
				}
				return t;
			}
		};
	}

	/**
	 * 如果是自增主键，则插入成功后会自动设置对象的主键值
	 * 
	 * @param t
	 * @return
	 */
	public boolean insert(final T t) {
		try {
			// 获取所有要插入到数据库的字段值
			final Object[] objs = new Object[this.insertFields.size()];
			for (int i = 0; i < this.insertFields.size(); i++) {
				objs[i] = insertFields.get(i).get(t);
			}

			// 执行插入
			if (!this.autokey) {// 非自增主键，执行插入即可
				return jt.update(sqlCache.get(INSERT_SQL_ID), objs) > 0;
			}
			// 自增主键，则返回主键值
			KeyHolder keyHolder = new GeneratedKeyHolder();

			int ok = jt.update(new PreparedStatementCreator() {
				@Override
				public PreparedStatement createPreparedStatement(Connection conn)
						throws SQLException {
					PreparedStatement ps = conn.prepareStatement(
							sqlCache.get(INSERT_SQL_ID),
							Statement.RETURN_GENERATED_KEYS);
					for (int i = 0; i < objs.length; i++) {
						ps.setObject(i + 1, objs[i]);
					}
					return ps;
				}
			}, keyHolder);
			if (ok <= 0) {
				return false;
			}
			this.keyfld.set(t, keyHolder.getKey());
			return true;
		} catch (Exception e) {
			e.printStackTrace();
		}
		return true;
	}

	/**
	 * 任意增加sql到cache中 <br />
	 * 为提高类的灵活性增加的注意，尽量修改类的适应性
	 * 
	 * @param sql
	 */
	public int addCachedSql(String sql) {
		this.sqlCache.add(sql);
		return sqlCache.size() - 1;
	}

	/**
	 * 根据主键查找对象
	 * 
	 * @param keyval
	 *            主键值
	 * @return
	 */
	public T select(Object keyval) {
		return jt.queryForObject("select * from " + this.tablename + " where "
				+ tblkey + "=?", rowMapper, new Object[] { keyval });
	}

	/**
	 * 多字段查找，多个符合条件的对象则只返回一个
	 * 
	 * @param fields
	 *            类中与数据库映射的字段
	 * @param vals
	 *            字段值
	 * @return
	 */
	public T select(Field[] fields, Object[] vals) {
		return jt.queryForObject(SQLCommand.SELECT.buildSql(tablename, obj2tbl,
				null, fields, null, false, new int[] { 1 }), rowMapper, vals);
	}

	/**
	 * 增加预置sql
	 * 
	 * @param fields
	 */
	public int addSelect(Field[] fields) {

		this.sqlCache.add(SQLCommand.SELECT.buildSql(tablename, obj2tbl, null,
				fields, null, false, new int[] { 1 }));
		return sqlCache.size() - 1;
	}

	/**
	 * 使用预置sql查找单个对象
	 */
	public T select(int sqlId, Object[] vals) {
		return jt.queryForObject(this.sqlCache.get(sqlId), rowMapper, vals);
	}

	/**
	 * 查找一组数据，支持order和limit
	 * 
	 * @param fields
	 * @param vals
	 * @param limit
	 * @param orderby
	 * @param asc
	 * @return
	 */
	public List<T> selectList(Field[] dest, Field[] where, Object[] vals,
			int[] limit, Field orderby, boolean asc) {
		String sql = SQLCommand.SELECT.buildSql(tablename, obj2tbl, dest,
				where, orderby, asc, limit);
		return jt.query(sql, vals, this.rowMapper);
	}

	/**
	 * 查找一组数据
	 * 
	 * @param fields
	 * @param vals
	 * @param limit
	 * @return
	 */
	public List<T> selectList(Field[] fields, Object[] vals, int[] limit) {
		return selectList(null, fields, vals, limit, null, false);
	}

	/**
	 * 增加批量数据查询sql
	 * 
	 * @param dest
	 * @param fields
	 * @param vals
	 * @param limit
	 * @param orderby
	 * @param asc
	 */
	public int addSelectList(Field[] dest, Field[] fields,
			Field orderby, boolean asc, int[] limit) {

		this.sqlCache.add(SQLCommand.SELECT.buildSql(tablename, obj2tbl, dest,
				fields, orderby, asc, limit));
		return sqlCache.size() - 1;
	}

	/**
	 * 预置sql查找一组数据
	 * 
	 * @param sqlId
	 * @param vals
	 * @return
	 */
	public List<T> selectList(int sqlId, Object[] vals) {
		return jt.query(sqlCache.get(sqlId), vals, this.rowMapper);
	}

	/**
	 * 主键删除
	 * 
	 * @param id
	 * @return
	 */
	public int delete(Object key) {
		return jt.update("delete from " + this.tablename + " where "
				+ this.tblkey + "=?", key);
	}

	/**
	 * 批量删除数据
	 */
	public int deletes(Field[] fields, Object[] vals, Field orderby,
			boolean asc, int[] limit) {
		String sql = SQLCommand.DELETE.buildSql(tablename, obj2tbl, null,
				fields, orderby, asc, limit);
		return jt.update(sql, vals);
	}

	/**
	 * 增加批量数据删除sql
	 * 
	 * @param dest
	 * @param fields
	 * @param orderby
	 * @param asc
	 * @param limit
	 */
	public int addDeletes(Field[] fields, Field orderby,
			boolean asc, int[] limit) {
		this.sqlCache.add(SQLCommand.DELETE.buildSql(tablename, obj2tbl, null,
				fields, orderby, asc, limit));
		return sqlCache.size();
	}

	/**
	 * 使用预置sql批量删除
	 * 
	 * @param sqlId
	 * @param vals
	 * @return
	 */
	public int deletes(int sqlId, Object[] vals) {
		return jt.update(sqlCache.get(sqlId), vals);
	}

	/**
	 * 更新指定主键数据的一个字段
	 * 
	 * @param t
	 * @param field
	 * @return
	 */
	public int update(T t, Field field) {
		String sql = "update " + tablename + " set " + this.obj2tbl.get(field)
				+ "=? where " + this.tblkey + "=?";
		try {
			return jt.update(sql, field.get(t), this.keyfld.get(t));
		} catch (Exception e) {
			e.printStackTrace();
			return -1;
		}
	}

	/**
	 * 更新指定主键数据的几个字段
	 * 
	 * @param t
	 * @param fields
	 * @return
	 */
	public int update(T t, Field[] fields) {
		return this.updates(t, fields, new Field[] { keyfld }, null, false,
				null);
	}

	/**
	 * 指定条件更新一批数据
	 * 
	 * @param t
	 * @param destflds
	 * @param whereflds
	 * @param orderby
	 * @param asc
	 * @param limit
	 * @return
	 */
	public int updates(T t, Field[] destflds, Field[] whereflds, Field orderby,
			boolean asc, int[] limit) {
		String sql = SQLCommand.UPDATE.buildSql(tablename, obj2tbl, destflds,
				whereflds, orderby, asc, limit);

		Object[] objs = new Object[destflds.length + 1];
		try {
			for (int i = 0; i < destflds.length; i++) {
				objs[i] = destflds[i].get(t);
			}
			objs[destflds.length] = this.keyfld.get(t);
		} catch (Exception e) {
			e.printStackTrace();
			return -1;
		}
		return jt.update(sql.toString(), objs);
	}

	/**
	 * 增加预置更新sql
	 * 
	 * @param dests
	 * @param where
	 * @param orderby
	 * @param asc
	 * @param limit
	 */
	public int addUpdates( Field[] dests, Field[] where,
			Field orderby, boolean asc, int[] limit) {
		this.sqlCache.add(SQLCommand.UPDATE.buildSql(tablename, obj2tbl, dests,
				where, orderby, asc, limit));
		return sqlCache.size() - 1;
	}

	/**
	 * 增加预置的以主键更新的sql
	 * 
	 * @param dests
	 */
	public int addUpdate(Field[] dests) {
		this.sqlCache.add(SQLCommand.UPDATE.buildSql(tablename, obj2tbl, dests,
				new Field[] { this.keyfld }, null, false, null));
		return sqlCache.size() - 1;
	}

	/**
	 * 预置的主键更新
	 * @param sqlId
	 * @param vals 注意vals的最后一个值是对象的主键值
	 * @return
	 */
	public int update(int sqlId,Object[] vals){
		return jt.update(sqlCache.get(sqlId), vals);
	}
	/**
	 * 使用预置sql批量更新
	 * 
	 * @param sqlId
	 * @param vals
	 * @return
	 */
	public int updates(int sqlId, Object[] vals) {
		return jt.update(sqlCache.get(sqlId), vals);
	}

	/**
	 * 此方法是开放给子类继承<br />
	 * 当T不能通过newInstance创建实例时候,重载这个方法进行可行的实例创建
	 * 
	 * @return
	 */
	protected T createInst() {
		try {
			return this.clsdefine.newInstance();
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}

}
